package oss

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"hash"
	"io"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

func getGmtIso8601(expireEnd int64) string {
	var tokenExpire = time.Unix(expireEnd, 0).UTC().Format("2006-01-02T15:04:05Z")
	return tokenExpire
}

type ConfigStrut struct {
	Expiration string          `json:"expiration"`
	Conditions [][]interface{} `json:"conditions"`
}

type PolicyToken struct {
	AccessKeyId string `json:"accessid"`
	Host        string `json:"host"`
	Expire      int64  `json:"expire"`
	Signature   string `json:"signature"`
	Policy      string `json:"policy"`
	Directory   string `json:"dir"`
	Callback    string `json:"callback"`
}

type CallbackParam struct {
	CallbackUrl      string `json:"callbackUrl"`
	CallbackBody     string `json:"callbackBody"`
	CallbackBodyType string `json:"callbackBodyType"`
}

func ParseSize(source string) int64 {
	s := strings.ToLower(source)
	reg := regexp.MustCompile(`^(\d)+([bkmgtp]?)$`)
	if reg.MatchString(s) {
		arr := reg.FindStringSubmatch(s)
		if len(arr) == 3 {
			num, _ := strconv.ParseInt(arr[1], 10, 64)
			switch arr[2] {
			case "k":
				return num * 1024
			case "m":
				return num * 1024 * 1024
			case "g":
				return num * 1024 * 1024 * 1024
			case "t":
				return num * 1024 * 1024 * 1024 * 1024
			case "p":
				return num * 1024 * 1024 * 1024 * 1024 * 1024
			default:
				return num
			}
		}
	}
	return 0
}
func GetPolicyToken(filename string) *PolicyToken {
	accessKeyId := os.Getenv("ali_access_key")
	accessKeySecret := os.Getenv("ali_access_sec")
	bucketName := os.Getenv("ali_oss_bucket")
	host := os.Getenv("static_url")
	var expireTime int64 = 1
	now := time.Now().Unix()
	expireEnd := now + expireTime
	var tokenExpire = getGmtIso8601(expireEnd)
	//create post policy json
	var config ConfigStrut
	config.Expiration = tokenExpire
	config.Conditions = append(config.Conditions, []interface{}{"eq", "$key", filename})
	config.Conditions = append(config.Conditions, []interface{}{"eq", "$bucket", bucketName})
	max := ParseSize(os.Getenv("file_max_size"))
	if max >= 1 {
		config.Conditions = append(config.Conditions, []interface{}{"content-length-range", 1, max})
	}
	result, _ := json.Marshal(config)
	deByte := base64.StdEncoding.EncodeToString(result)
	h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(accessKeySecret))
	_, _ = io.WriteString(h, deByte)
	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
	var callbackParam CallbackParam
	callbackParam.CallbackUrl = os.Getenv("ali_oss_callback")
	callbackParam.CallbackBody = `filename=${object}`
	callbackParam.CallbackBodyType = "application/x-www-form-urlencoded"
	callbackStr, _ := json.Marshal(callbackParam)
	callbackBase64 := base64.StdEncoding.EncodeToString(callbackStr)
	policyToken := &PolicyToken{}
	policyToken.AccessKeyId = accessKeyId
	policyToken.Host = host
	policyToken.Expire = expireEnd
	policyToken.Signature = string(signedStr)
	policyToken.Directory = filename
	policyToken.Policy = string(deByte)
	policyToken.Callback = string(callbackBase64)
	return policyToken
}
func UploadInfo(filename string) map[string]interface{} {
	p := GetPolicyToken(filename)
	host := os.Getenv("static_url")
	d := map[string]interface{}{
		"host":                  host,
		"url":                   fmt.Sprintf("%s/%s", host, filename),
		"key":                   filename,
		"OSSAccessKeyId":        p.AccessKeyId,
		"success_action_status": "200",
		"policy":                p.Policy,
		"signature":             p.Signature,
	}
	return d
}
func CallbackHandler(templates ...string) http.HandlerFunc {
	template := ""
	if len(templates) > 0 {
		template = templates[0]
	} else {
		template = fmt.Sprintf(`{"url":"%s/${filename}"}`, os.Getenv("static_url"))
	}
	return func(w http.ResponseWriter, req *http.Request) {

		t := template
		for _, r := range regexp.MustCompile(`\$\{(\w+)\}`).FindAllStringSubmatch(template, -1) {
			if len(r) == 2 {
				v := req.PostFormValue(r[1])
				if v != "" {
					t = strings.ReplaceAll(t, r[0], v)
				}
			}
		}
		_, _ = w.Write([]byte(t))
	}
}
